package nl.infodation.emr.pricingservice.controller;

import static org.apache.commons.lang.StringUtils.substringAfterLast;

import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import javax.persistence.Column;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Id;
import javax.persistence.PersistenceException;
import javax.persistence.Query;

import nl.infodation.emr.pricingservice.entity.Environment;
import nl.infodation.emr.pricingservice.entity.LanguageSystem;
import nl.infodation.emr.pricingservice.entity.Role;
import nl.infodation.emr.pricingservice.entity.SystemValue;
import nl.infodation.emr.pricingservice.entity.User;
import nl.infodation.emr.pricingservice.ui.page.SessionData;
import nl.infodation.emr.psrefdata.entity.Item;
import nl.infodation.emr.psrefdata.entity.Product;
import nl.infodation.emr.utility.Constants;
import nl.infodation.emr.utility.EntityManagerFactoryUtil;

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.googlecode.genericdao.dao.jpa.GenericDAOImpl;
import com.googlecode.genericdao.search.ExampleOptions;
import com.googlecode.genericdao.search.Search;
import com.googlecode.genericdao.search.jpa.JPAAnnotationMetadataUtil;
import com.googlecode.genericdao.search.jpa.JPASearchProcessor;
import com.vaadin.data.util.BeanItem;

@SuppressWarnings({"unchecked", "rawtypes"})
public abstract class AbstractBaseController<T, Tsearch, Tid extends Serializable> extends GenericDAOImpl<T, Serializable> implements
		IController<T, Tsearch, Tid> {

	
	protected static final Logger LOG = LoggerFactory
			.getLogger(AbstractBaseController.class);
	protected EntityManagerFactory EM_FACTORY = EntityManagerFactoryUtil
			.getEntityManagerFactory(Constants.PRICING_PERSISTENCE_UNIT);
	protected static String queryObjChar;
	protected static StringBuilder queryString = new StringBuilder(100);

	private ExampleOptions exampleOptions = createExampleOptions();
	
	protected abstract T buildCriteria(Tsearch search);
	
	protected EntityManager createEntityManager(EntityManagerFactory EntityManagerFactory) {
		return EntityManagerFactory.createEntityManager();
	}
	
	protected void closeEntityManager(EntityManager entityManager) {
		if (entityManager != null && entityManager.isOpen()) {
			entityManager.close();
		}
	}
	
	protected void rollbackEntityManager(EntityManager entityManager) {
		if (entityManager != null
				&& entityManager.getTransaction().isActive()) {
			entityManager.getTransaction().rollback();
		}
	}

	@Override
	public boolean deleteById(Tid id) {
		if (null == id) {
			return Boolean.FALSE;
		}
		boolean removed = Boolean.FALSE;
		EntityManager entityManager = createEntityManager(EM_FACTORY);
		super.setEntityManager(entityManager);
		T entity = super.find(id);
		try {
			super.em().getTransaction().begin();
				super.remove(entity);
			super.em().getTransaction().commit();
		} catch (IllegalStateException | PersistenceException ex) {
			LOG.error("Can't delete '" + super.persistentClass.getName() + "' entity with id = {" + id + "}");
			rollbackEntityManager(super.em());
		} finally {
			closeEntityManager(super.em());
		}
		return removed;
	}

	@Override
	public boolean delete(T t) {
		if (null == t) {
			return false;
		}
		super.setSearchProcessor(new JPASearchProcessor(new JPAAnnotationMetadataUtil()));
		Serializable id = super.getMetadataUtil().getId(t);
		return deleteById((Tid)id);
	}
	
	@Override
	public List<T> findAll() {
		return super.findAll();
	}

	@Override
	public List<BeanItem> getPaginationList(int startPage, int pageSize) {
		Class<Tsearch> searchClass = null;
		try {
			 searchClass = getSearchClass();
			 return getPaginationByCriteria(searchClass.newInstance(), startPage, pageSize);
		} catch(InstantiationException | IllegalAccessException ex) {
			if (null != searchClass) {
				LOG.error(searchClass.getName() + " type invalid!", ex);
			}
		}
		return new ArrayList<BeanItem>();
	}

	@Override
	public List<BeanItem> getPaginationByCriteria(Tsearch t, int startPage,
			int pageSize) {
		
		if (null == t) {
			return new ArrayList<BeanItem>();
		}
		
		final List<BeanItem> beans = new ArrayList<BeanItem>();
		
		EntityManager entityManager = createEntityManager(EM_FACTORY);
		super.setEntityManager(entityManager);
		try {
			super.setSearchProcessor(new JPASearchProcessor(new JPAAnnotationMetadataUtil()));
			T example = buildCriteria(t);
			
			Search search = new Search(super.persistentClass);
			search.addFilter(super._getFilterFromExample(example, exampleOptions));
			if (startPage > 0) {
				search.setFirstResult((startPage - 1) * pageSize);
			} else {
				search.setFirstResult(0);
			}
			search.setMaxResults(pageSize);
			
			final List<T> entities = super.search(search);
			if (!entities.isEmpty()) {
				for (T entity : entities) {
					beans.add(new BeanItem<T>(entity));
				}
			}
		} finally {
			closeEntityManager(super.em());
		}
		return beans;
	}

	@Override
	public T getObjectById(Tid id) {
		if (null == id) {
			return null;
		}
		T entity = null;
		
		EntityManager entityManager = createEntityManager(EM_FACTORY);
		super.setEntityManager(entityManager);
		try {
			entity = super.find(id);
		} finally {
			closeEntityManager(super.em());
		}
		return entity;
	}

	@Override
	public List<T> getEntitiesByIDs(List<Tid> ids) {
		List<T> result = new ArrayList<>();
		
		if (ids != null && ids.size() > 0) {
			EntityManager entityManager = createEntityManager(EM_FACTORY);
			
			Query query = entityManager.createQuery("select p from " + getSimpleName() + " p where p." + getIdentifyName(entityManager) + " in :ids");
			query.setParameter("ids", ids);
			result = query.getResultList();
		}
		
		return result;
	}

	@Override
	public long getTotalNumberItem() {
		int count = 0;
		EntityManager entityManager = createEntityManager(EM_FACTORY);
		super.setEntityManager(entityManager);
		try {
			super.setSearchProcessor(new JPASearchProcessor(new JPAAnnotationMetadataUtil()));
			count = super._count(super.persistentClass);
		} finally {
			closeEntityManager(super.em());
		}
		return count;
	}
	
	private ExampleOptions createExampleOptions() {
		ExampleOptions exampleOptions = new ExampleOptions();
		exampleOptions.setLikeMode(getLikeMode());
		exampleOptions.setExcludeProps(getExcludeProps());
		exampleOptions.setExcludeZeros(getExcludeZeros());
		return exampleOptions;
	}
	
	protected boolean getExcludeZeros() {
		return Boolean.FALSE;
	}

	@Override
	public boolean copyToEnvironment(int id, boolean clean) {
		// TODO Auto-generated method stub
		return false;
	}
	
	protected void buildMoreQuery() {
		// TODO Auto-generated method stub
	}

	protected int getLikeMode() {
		return ExampleOptions.EXACT;
	}

	protected Collection<String> getExcludeProps() {
		return new ArrayList<String>();
	}
	
	@Override
	public long getTotalNumberItemByCriteria(Tsearch t) {
		if (null == t) {
			return 0;
		}
		
		int count = 0;
		EntityManager entityManager = createEntityManager(EM_FACTORY);
		super.setEntityManager(entityManager);
		try {
			super.setSearchProcessor(new JPASearchProcessor(new JPAAnnotationMetadataUtil()));
			T example = buildCriteria(t);
			Search search = new Search(super.persistentClass);
			search.addFilter(super._getFilterFromExample(example, exampleOptions));
			count = super.count(search);
		} finally {
			closeEntityManager(super.em());
		}
		return count;
	}

	@Override
	public boolean saveObject(T t) {
		boolean saved = Boolean.FALSE;
		EntityManager entityManager = createEntityManager(EM_FACTORY);
		super.setEntityManager(entityManager);
		super.setSearchProcessor(new JPASearchProcessor(new JPAAnnotationMetadataUtil()));
		try {
			super.em().getTransaction().begin();
				super.save(t);
			super.em().getTransaction().commit();
			saved = Boolean.TRUE;
		} catch (IllegalStateException | PersistenceException ex) {
			Serializable id = super.getMetadataUtil().getId(t);
			LOG.error("Can't save '" + super.persistentClass.getName() + "' entity with id = {" + id + "}");
			rollbackEntityManager(super.em());
		} finally {
			closeEntityManager(super.em());
		}
		return saved;
	}
	
	/* (non-Javadoc)
	 * @see nl.infodation.emr.pricingservice.controller.IController#save(java.util.List)
	 */
	@Override
	public boolean save(List<T> entities) {
		boolean saved = Boolean.FALSE;
		
		if (entities == null || entities.size() == 0) {
			return Boolean.FALSE;
		}
		
		EntityManager entityManager = createEntityManager(EM_FACTORY);
		super.setEntityManager(entityManager);
		try {
			super.em().getTransaction().begin();
				Iterator<T> itorator = entities.iterator();
				while (itorator.hasNext()) {
					save(entityManager, itorator.next());
				}
			super.em().getTransaction().commit();
			saved = Boolean.TRUE;
		} catch (IllegalStateException | PersistenceException ex) {
			LOG.error("Can't save '" + super.persistentClass.getName() + "' entities with SIZE = {" + entities.size() + "}");
			rollbackEntityManager(super.em());
		} finally {
			closeEntityManager(super.em());
		}
		
		return saved;
	}
	
	private void save(EntityManager entityManager, T entity) {
		if (entityManager.getEntityManagerFactory().getPersistenceUnitUtil()
				.getIdentifier(entity) != null) {
			entityManager.merge(entity);
		} else {
			entityManager.persist(entity);
		}
	}

	/* (non-Javadoc)
	 * @see nl.infodation.emr.pricingservice.controller.IController#findFirstByEntity(java.lang.Object)
	 */
	@Override
	public T findFirstByEntity(T t) {
		List<T> list = findByEntity(t, 0, Integer.MAX_VALUE);
		if (null != list && !list.isEmpty()) {
			return list.get(0);
		}
		return null;
	}
	
	/* (non-Javadoc)
	 * @see nl.infodation.emr.pricingservice.controller.IController#findByEntity(java.lang.Object)
	 */
	@Override
	public List<T> findByEntity(T t) {
		return findByEntity(t, 0, Integer.MAX_VALUE);
	}

	/* (non-Javadoc)
	 * @see nl.infodation.emr.pricingservice.controller.IController#findByEntity(java.util.Hashtable, int, int)
	 */
	@Override
	public List<T> findByEntity(T t, int firstResult, int maxResults) {
		if (null == t) {
			return new ArrayList<T>();
		}
		
		List<T> entities = null;
		
		EntityManager entityManager = createEntityManager(EM_FACTORY);
		super.setEntityManager(entityManager);
		try {
			super.setSearchProcessor(new JPASearchProcessor(new JPAAnnotationMetadataUtil()));
			
			Search search = new Search(super.persistentClass);
			search.addFilter(super._getFilterFromExample(t, exampleOptions));
			search.setFirstResult(firstResult);
			search.setMaxResults(maxResults);
			
			entities = super.search(search);
		} finally {
			closeEntityManager(super.em());
		}
		
		return entities;
	}

	protected void setEntityManagerFactoryForRole() {
		// Process for role
		String roleName = SessionData.getRoleName();
		if (SessionData.isSuperAdmin()) {
			String bus = SessionData.getRoleNameForRefData();
			if (Constants.AFM_ROLE.equals(bus)) {
				EM_FACTORY = EntityManagerFactoryUtil
						.getEntityManagerFactory(Constants.AFM_PSREFDATA_PERSISTENCE_UNIT);
			} else if (Constants.PROVEA_ROLE.equals(bus)) {
				EM_FACTORY = EntityManagerFactoryUtil
						.getEntityManagerFactory(Constants.PROVEA_PSREFDATA_PERSISTENCE_UNIT);
			}
		}
		if (Constants.PROVEA_ROLE.equals(roleName)) {
			EM_FACTORY = EntityManagerFactoryUtil
					.getEntityManagerFactory(Constants.PROVEA_PSREFDATA_PERSISTENCE_UNIT);

		} else if (Constants.AFM_ROLE.equals(roleName)) {
			EM_FACTORY = EntityManagerFactoryUtil
					.getEntityManagerFactory(Constants.AFM_PSREFDATA_PERSISTENCE_UNIT);
		}
	}
	
	protected void setEntityManagerFactoryForRolePopup(String roleName) {
		if (Constants.PROVEA_ROLE.equals(roleName)) {
			EM_FACTORY = EntityManagerFactoryUtil
					.getEntityManagerFactory(Constants.PROVEA_PSREFDATA_PERSISTENCE_UNIT);

		} else if (Constants.AFM_ROLE.equals(roleName)) {
			EM_FACTORY = EntityManagerFactoryUtil
					.getEntityManagerFactory(Constants.AFM_PSREFDATA_PERSISTENCE_UNIT);
		}
	}

	private String resolveName(final String name) {
		return substringAfterLast(name, ".");
	}

	/**
	 * Append the given string to the query.
	 * 
	 * @see #queryString
	 * @see #buildQueryAndParams(Object)
	 * @see #buildSelect(Object)
	 * @see #buildSelect(Object)
	 */
	@Deprecated
	protected void append(final String newVal) {
		queryString.append(newVal);
	}

	@Deprecated
	private boolean isContainingValue(final Object value) {
		boolean containsValue = false;
		if (value != null) {
			if (value instanceof String) {
				containsValue ^= !String.class.cast(value).trim().isEmpty();

			} else if (value instanceof Integer) {
				if ((Integer) value != 0)
					containsValue = true;

			} else if (value instanceof Long) {
				if ((Long) value != 0)
					containsValue = true;

			} else if (value instanceof Environment) {
				containsValue = true;

			} else if (value instanceof Product) {
				containsValue = true;

			} else if (value instanceof Item) {
				containsValue = true;

			} else if (value instanceof Role) {
				containsValue = true;

			}  else if (value instanceof LanguageSystem) {
				containsValue = true;
				
			} else if (value instanceof User) {
				containsValue = true;

			} else if (value instanceof SystemValue) {
				containsValue = true;

			} else {
				containsValue = false;
			}
		}
		return containsValue;
	}

	@Deprecated
	protected abstract Boolean isObject(Field field);

	@Deprecated
	private boolean isIdFieldContainsValue(final Field[] subFields,
			final Object type) throws IllegalAccessException {
		boolean containsValue = true;
		if (subFields != null) {
			for (Field field : subFields) {
				for (Annotation annotation : field.getDeclaredAnnotations()) {
					if (annotation instanceof Id) {
						field.setAccessible(true);
						if (!isContainingValue(field.get(type))) {
							containsValue = false;
						}
					}
				}
			}
		}
		return containsValue;
	}

	@Deprecated
	private Map<Field, Annotation> getSearchFields(final T beanType)
			throws IllegalAccessException {
		final HashMap<Field, Annotation> fields = new HashMap<Field, Annotation>();
		for (Field field : beanType.getClass().getDeclaredFields()) {
			for (Annotation annotation : field.getDeclaredAnnotations()) {
				field.setAccessible(true); // so we can access the private
											// fields for bean type
				if (isContainingValue(field.get(beanType))) {
					if (isObject(field)) {
						if (isIdFieldContainsValue(field.getType()
								.getDeclaredFields(), field.get(beanType))) {
							fields.put(field, annotation);
						}
					} else {
						fields.put(field, annotation);
					}
				}
			}
		}
		return fields;
	}

	@Deprecated
	protected void buildQueryAndParams(final T beanType)
			throws IllegalAccessException {
		final Map<Field, Annotation> fields = getSearchFields(beanType);
		if (!fields.isEmpty()) {
			append(" WHERE ");
		}
		int index = 0;
		final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd",
				Locale.US);
		for (Field field : fields.keySet()) {
			index++;
			if (isObject(field)) {
				// if field described as object get its fields
				final Field[] subFields = field.getType().getDeclaredFields();
				int subIndex = 0;
				for (Field subField : subFields) {
					subIndex++;
					final Column column = subField.getAnnotation(Column.class);
					if (column != null) {
						subField.setAccessible(true);
						if (isContainingValue(subField.get(field.get(beanType)))) {
							append(queryObjChar + field.getName() + "."
									+ subField.getName() + "  LIKE '%"
									+ subField.get(field.get(beanType)) + "%'");
							if (subIndex != subFields.length
									|| index != fields.keySet().size()) {
								append(" AND ");
							}
						}
					}
				}
			} else {
				if (field.get(beanType) instanceof Date) {
					final String value = format.format(field.get(beanType));
					append(queryObjChar + field.getName() + " LIKE '%" + value
							+ "%'");
				} else if (field.get(beanType) instanceof Boolean) {
					final String oprt = (field.get(beanType).toString()
							.toLowerCase().equals("false")) ? "=" : "<>";
					append(queryObjChar + field.getName() + " " + oprt + " 0");
				} else {
					append(queryObjChar + field.getName() + " LIKE '%"
							+ field.get(beanType) + "%'");
				}
				if (index != fields.keySet().size()) {
					append(" AND ");
				}
			}
		}
	}

	@Deprecated
	protected void buildSelect(final T beanType) {
		if (beanType == null) {
			throw new IllegalArgumentException("Bean should be passed.");
		}
		queryObjChar = resolveName(beanType.getClass().getName()).substring(0,
				1).toLowerCase();
		append("SELECT " + queryObjChar + " FROM "
				+ resolveName(beanType.getClass().getName()) + " "
				+ queryObjChar);
		queryObjChar = queryObjChar + ".";
	}

	@Deprecated
	protected void buildSelect() {
		queryObjChar = getSimpleName().substring(0, 1).toLowerCase();
		append("SELECT " + queryObjChar + " FROM "
				+ getSimpleName() + " "
				+ queryObjChar);
		queryObjChar = queryObjChar + ".";
	}

	@Deprecated
	protected void buildSelectCount(final T beanType) {
		if (beanType == null) {
			throw new IllegalArgumentException("Bean should be passed.");
		}
		queryObjChar = resolveName(beanType.getClass().getName()).substring(0,
				1).toLowerCase();
		append("SELECT COUNT(" + queryObjChar + ") FROM "
				+ resolveName(beanType.getClass().getName()) + " "
				+ queryObjChar);
		queryObjChar = queryObjChar + ".";
	}

	private Class<Tsearch> getSearchClass() {
		return ((Class<Tsearch>) ((ParameterizedType) getClass()
				.getGenericSuperclass()).getActualTypeArguments()[1]);
	}
	
	private Class<T> getEntityClass() {
		return ((Class<T>) ((ParameterizedType) getClass()
				.getGenericSuperclass()).getActualTypeArguments()[0]);
	}
		
	private String getIdentifyName(EntityManager entityManager) {
		Class<Tid> idClass = ((Class<Tid>) ((ParameterizedType) getClass()
				.getGenericSuperclass()).getActualTypeArguments()[2]);
		return entityManager.getMetamodel().entity(getEntityClass()).getId(idClass).getName();
	}
	
	private String getSimpleName() {
		return super.persistentClass.getSimpleName();
	}
	
	protected void rollback(EntityManager entityManager) {
		if (entityManager != null && entityManager.getTransaction() != null) {
			entityManager.getTransaction().rollback();
		}
	}
	
	protected void close(EntityManager entityManager) {
		if (entityManager != null && entityManager.isOpen()) {
			entityManager.close();
		}
	}
	
	protected String like(String value) {
		if (value == null) {
			value = "";
		}
		StringBuilder strBuilder = new StringBuilder();
		return strBuilder.append("%").append(value).append("%").toString();
	}
	
	protected void likeOrEmpty(Query query, String key, String value) {
		if (StringUtils.isNotBlank(value)) {
			query.setParameter(key, like(value));
		} else {
			query.setParameter(key, like(value));				
		}
	}

	protected void equalsOrEmpty(Query query, String key, String value) {
		if (StringUtils.isNotBlank(value)) {
			query.setParameter(key, value);
		} else {
			query.setParameter(key, like(value));				
		}
	}
	
	protected String getStrValue(Object value) {
		if (value == null) {
			return "";
		} else {
			return String.valueOf(value);
		}
	}
	
}
